package util;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import plume.Pair;
import plume.UtilMDE;

public class CanBeUsedAs {
	  private static Map<Pair<Class<?>, Class<?>>, Boolean> canBeUsedCache =
		     new LinkedHashMap<Pair<Class<?>, Class<?>>, Boolean>();

	  public static boolean canBeUsedAs(Class<?> c1, Class<?> c2) {
		     if (c1 == null || c2 == null)
		       throw new IllegalArgumentException("Parameters cannot be null.");
			 if(c1.getName().equals("java.lang.Object")||c1.getName().equals("java.util.HashMap")
					|| c1.getName().equals("java.lang.Comparable" ) )
				 	return false;
			 if(c2.equals(java.lang.Comparable.class)&& ! (c1 ==  java.lang.Integer.class||c1 == int.class)) 
				 return false;

//		     if (c1.equals(c2))
//		       return true;
		     if (c1.equals(void.class) && c2.equals(void.class))
		       return true;
		     if (c1.equals(void.class) || c2.equals(void.class))
		       return false;
		     Pair<Class<?>, Class<?>> classPair = new Pair<Class<?>, Class<?>>(c1, c2);
		     Boolean cachedRetVal = canBeUsedCache.get(classPair);
		     boolean retval;
		     if (cachedRetVal == null) {
		       retval = canBeUsedAs0(c1, c2);
		       canBeUsedCache.put(classPair, retval);
		     } else {
		       retval = cachedRetVal;
		     }
		     return retval;
		   }

		   // TODO testclasses array code (third if clause)
		   private static boolean canBeUsedAs0(Class<?> c1, Class<?> c2) {
			   if(c2.getName().contains("java.lang.C")&&c1.getName().contains("RBTree.RedBlackTree") )
				   System.out.println("");
		     if (c1.isArray()) {
		       if (c2.equals(Object.class))
		         return true;
		       if (!c2.isArray())
		         return false;
		       Class<?> c1SequenceType = c1.getComponentType();
		       Class<?> c2componentType = c2.getComponentType();

		       if (c1SequenceType.isPrimitive()) {
		         if (c2componentType.isPrimitive()) {
		           return (c1SequenceType.equals(c2componentType));
		         } else {
		           return false;
		         }
		       } else {
		         if (c2componentType.isPrimitive()) {
		           return false;
		         } else {
		           c1 = c1SequenceType;
		           c2 = c2componentType;
		         }
		       }
		     }

		     if (c1.isPrimitive())
		       c1 = CanBeUsedAs.boxedType(c1);
		     if (c2.isPrimitive())
		       c2 = CanBeUsedAs.boxedType(c2);

		     boolean ret = false;

		     if (c1.equals(c2)) { // XXX redundant (see canBeUsedAs(..)).
		       ret = true;
		     } else if (c2.isInterface()) {
		       Set<Class<?>> c1Interfaces = getInterfacesTransitive(c1);
		       if (c1Interfaces.contains(c2))
		         ret = true;
		       else
		         ret = false;
		     } else if (c1.isInterface()) {
		       // c1 represents an interface and c2 a class.
		       // The only safe possibility is when c2 is Object.
		       if (c2.equals(Object.class))
		         ret = true;
		       else
		         ret = false;
		     } else {
		       ret = isSubclass(c1, c2);
		     }
		     return ret;
		   }
		   private static boolean isSubclass(Class<?> c1, Class<?> c2) {
			     assert(c1 != null);
			     assert(c2 != null);
			     assert(!c1.equals(Void.TYPE));
			     assert(!c2.equals(Void.TYPE));
			     assert(!c1.isInterface());
			     assert(!c2.isInterface());
			     return c2.isAssignableFrom(c1);
			   }
		   private static Set<Class<?>> getInterfacesTransitive(Class<?> c1) {

			     Set<Class<?>> ret = new LinkedHashSet<Class<?>>();

			     Class<?>[] c1Interfaces = c1.getInterfaces();
			     for (int i = 0; i < c1Interfaces.length; i++) {
			       ret.add(c1Interfaces[i]);
			       ret.addAll(getInterfacesTransitive(c1Interfaces[i]));
			     }

			     Class<?> superClass = c1.getSuperclass();
			     if (superClass != null)
			       ret.addAll(getInterfacesTransitive(superClass));

			     return ret;
			   }
		   private static final Map<Class<?>, Class<?>> primitiveAndStringToBoxed
		   = new LinkedHashMap<Class<?>, Class<?>>(8);

		   static {
		     primitiveAndStringToBoxed.put(boolean.class, Boolean.class);
		     primitiveAndStringToBoxed.put(byte.class, Byte.class);
		     primitiveAndStringToBoxed.put(char.class, Character.class);
		     primitiveAndStringToBoxed.put(double.class, Double.class);
		     primitiveAndStringToBoxed.put(float.class, Float.class);
		     primitiveAndStringToBoxed.put(int.class, Integer.class);
		     primitiveAndStringToBoxed.put(long.class, Long.class);
		     primitiveAndStringToBoxed.put(short.class, Short.class);
		     primitiveAndStringToBoxed.put(String.class, String.class); // TODO remove this hack!
		   }
		   public  static Class<?> boxedType(Class<?> c1) {
			    return primitiveAndStringToBoxed.get(c1);
			  }


}
